package org.zetaframework.base.excel.handler;

import cn.idev.excel.metadata.Head;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.write.handler.CellWriteHandler;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.Arrays;
import java.util.List;

/**
 * 自定义合并单元格处理器
 * <p>
 * 参考：
 * <a href="https://juejin.cn/post/7372080507260059685">文档</a>
 * @author gcc
 */
public class MergeHandler implements CellWriteHandler {
    /** 合并列索引 */
    private final int[] mergeColumnIndex;
    /** 合并行索引 */
    private final int mergeRowIndex;

    /**
     * 自定义合并单元格处理器
     *
     * @param mergeRowIndex 从第几行开始合并。索引从0开始
     * @param mergeColumnIndex 需要合并的列的索引
     */
    public MergeHandler(int mergeRowIndex, int[] mergeColumnIndex) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndex = mergeColumnIndex;
    }

    /**
     * 在单元格排列好之后执行
     */
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        int curRowIndex = cell.getRowIndex();
        int curColIndex = cell.getColumnIndex();

        // 判断当前行是否需要合并
        if (curRowIndex > mergeRowIndex && isMergeColumn(curColIndex)) {
            mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
        }
    }

    /**
     * 判断当前列是否需要合并
     *
     * @param curColIndex 当前列索引
     * @return  boolean
     */
    private boolean isMergeColumn(int curColIndex) {
        // 判断当前列索引是否在指定的列索引中·
        return Arrays.stream(mergeColumnIndex)
                .anyMatch(index -> index == curColIndex);
    }

    /**
     * 根据上一行数据，判断当前行是否需要合并
     *
     * @param writeSheetHolder 工作表持有者对象，用于获取Sheet对象
     * @param cell             当前单元格对象
     * @param curRowIndex      当前行索引
     * @param curColIndex      当前列索引
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        // 获取当前单元格数据
        Object curData = getCellData(cell);

        // 获取上一行单元格数据
        Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
        Object preData = getCellData(preCell);

        // 如果当前单元格数据与上一行单元格数据相同，则合并单元格
        if (curData.equals(preData)) {
            Sheet sheet = writeSheetHolder.getSheet();
            mergeCells(sheet, curRowIndex, curColIndex);
        }
    }

    /**
     * 获取单元格数据
     *
     * @param cell 当前单元格对象
     * @return 当前单元格数据
     */
    private Object getCellData(Cell cell) {
        return cell.getCellType() == CellType.STRING
                ? cell.getStringCellValue()
                : cell.getNumericCellValue();
    }

    /**
     * 合并单元格
     *
     * @param sheet       工作表对象
     * @param curRowIndex 当前行索引
     * @param curColIndex 当前列索引
     */
    private void mergeCells(Sheet sheet, int curRowIndex, int curColIndex) {
        // 获取已存在的合并区域列表
        List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();

        boolean isMerged = false;
        // 遍历所有合并区域，检查是否可以扩展已有合并区域
        for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
            CellRangeAddress cellRangeAddr = mergeRegions.get(i);
            // 判断上一行同一列是否在某个合并区域内
            if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
                // 如果在，则移除旧的合并区域，并扩展其结束行到当前行
                sheet.removeMergedRegion(i);
                cellRangeAddr.setLastRow(curRowIndex);
                sheet.addMergedRegion(cellRangeAddr);
                isMerged = true;
            }
        }

        // 如果没有找到可扩展的合并区域，则新建一个合并区域
        if (!isMerged) {
            CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
            sheet.addMergedRegion(cellRangeAddress);
        }
    }

}
